#include "StdAfx.h"
#include "GooglePlusOneButtonToolbar.h"

const DWORD TOOLBAR_STYLE = 
		/*Window styles:*/  WS_CHILD | WS_CLIPSIBLINGS | WS_VISIBLE | WS_TABSTOP |
		/*Toolbar styles:*/ TBSTYLE_TOOLTIPS | TBSTYLE_FLAT | TBSTYLE_TRANSPARENT | TBSTYLE_LIST | TBSTYLE_CUSTOMERASE |
		/*Common Control styles:*/ CCS_TOP | CCS_NODIVIDER | CCS_NOPARENTALIGN | CCS_NORESIZE;


DEFINE_LOGGER(CGooglePlusOneButtonToolbar)

CriticalSection CGooglePlusOneButtonToolbar::m_crsecInit;
bool CGooglePlusOneButtonToolbar::m_bInitialized = false;

TBBUTTON	CGooglePlusOneButtonToolbar::m_buttons[1];
CManagedImageList CGooglePlusOneButtonToolbar::m_image_list;

CString CGooglePlusOneButtonToolbar::m_google_plus_one_api_url;
CString CGooglePlusOneButtonToolbar::m_google_plus_one_frame_domain;
LPCWSTR CGooglePlusOneButtonToolbar::DIV_ID = L"GooglePlusOneButtonByPrettyToolsDiv";
CStringW CGooglePlusOneButtonToolbar::M_TRUE(L"true");

CGooglePlusOneButtonToolbar::CGooglePlusOneButtonToolbar(void)
    : m_nTimerId(0)
    , m_nTimerRepeatCount(0)
    , m_nIconIndex(-1)
{
}

CGooglePlusOneButtonToolbar::~CGooglePlusOneButtonToolbar(void)
{
	if (IsWindow()) DestroyWindow();
}

void CGooglePlusOneButtonToolbar::Init()
{
	LOCK(m_crsecInit);
	if (m_bInitialized)
		return;

	LOG("Initialization...");

    m_google_plus_one_api_url.LoadString(IDS_GOOGLE_PLUS_ONE_API_URL);
    m_google_plus_one_frame_domain.LoadString(IDS_GOOGLE_PLUS_ONE_FRAME_DOMAIN);

	int icons[] = { IDB_GRAY, IDB_RED };
	size_t nIconsCount = sizeof(icons)/sizeof(icons[0]);

	m_image_list.Create(19,16, ILC_COLOR24, nIconsCount, 1);

	for (size_t i = 0; i < nIconsCount; ++i)
	{
        CBitmap bmp;
        if (!bmp.LoadBitmap(icons[i]))
		{
			LOG("ERROR - Cannot load icon 0x%x", icons[i]);
			continue;
		}

		m_image_list.Add(bmp);
	}

	LOG("Initialization buttons...");

	ZeroMemory(m_buttons, sizeof(m_buttons));

	m_buttons[0].idCommand = IDM_GOOGLEPLUSONE;
	m_buttons[0].fsState = TBSTATE_ENABLED;
	m_buttons[0].fsStyle = BTNS_BUTTON | BTNS_AUTOSIZE;
	m_buttons[0].iString = -1;
	m_buttons[0].iBitmap = 0;

	m_bInitialized = true;
	LOG("Initialization done");
}

bool CGooglePlusOneButtonToolbar::Create(const CWindow &wndParent)
{
	try
	{
		Init();

		CRect rect(0,0,0,0);
		wndParent.GetClientRect(&rect);
		rect.bottom = rect.top + 22;
		if (rect.right - rect.left < 285)
			rect.right = rect.left + 285;

		LOG("Creating...");
		if (!CWindowImpl<CGooglePlusOneButtonToolbar, CToolBarCtrl>::Create(wndParent, rect, NULL, TOOLBAR_STYLE))
		{
			LOG("ERROR on creating window!");
			return false;
		}

		LOG("Window has been created 0x%x for 0x%x. Initialization...", (DWORD)m_hWnd, (DWORD)this);
		// buttons with images and text
		SetExtendedStyle(TBSTYLE_EX_MIXEDBUTTONS|TBSTYLE_EX_DRAWDDARROWS);
		// Sets the size of the TBBUTTON structure.
		SetButtonStructSize();
		// Set the maximum number of text rows and bitmap size.
		SetMaxTextRows(16);

		LOG("Initialization image list for 0x%x...", this);
		// Set the toolbar's image
		SetImageList(m_image_list);

		LOG("Adding buttons for 0x%x...", this);
		AddButtons(sizeof(m_buttons)/sizeof(TBBUTTON), m_buttons);

		LOG("Initialization 0x%x done", (DWORD)m_hWnd);

		return true;
	}
	catch(...)
	{
		LOG("EXCEPTION on creation 0x%x", this);
	}
	return false;
}

void CGooglePlusOneButtonToolbar::SetBrowser(IWebBrowser2* pBrowser)
{
	m_browser = pBrowser;
}

#define TOOLBAR_WIDTH	32

int CGooglePlusOneButtonToolbar::GetMaxWidth()
{
	int iSize = 0;
	for (int i = 0, count = GetButtonCount(); i < count; ++i)
	{
		TBBUTTON button;
		if (!GetButton(i, &button))
			{ iSize = TOOLBAR_WIDTH; break; }

		if (button.fsState & TBSTATE_HIDDEN)
			continue;

		CRect rect;
		if (GetItemRect(i, &rect))
			iSize += rect.Width();
		else
			{ iSize = TOOLBAR_WIDTH; break; }
	}

	return iSize + 4;
}

LRESULT CGooglePlusOneButtonToolbar::OnToolTip(LPNMHDR pnmh)
{
#define lpnmtdi ((LPNMTTDISPINFO)pnmh)

	switch (pnmh->idFrom)
	{
	case IDM_GOOGLEPLUSONE:
        lpnmtdi->lpszText = (LPTSTR)(LPCTSTR)m_strTooltip;
		break;
	}

	return 0;
}

void CGooglePlusOneButtonToolbar::OnButtonPresed()
{
    HTML::Element b = FindPlusOneButton();
    if (b)
    {
        bool pressed = IsPressed(b);

        b.click();
        StartTimer();

        if (!pressed)
            OpenShareDialog();
    }
    else
    {
        OpenShareDialog();
    }
}

void CGooglePlusOneButtonToolbar::OpenShareDialog()
{
    if (!m_browser || !m_browser.document || !m_browser.document.location || !m_browser.document.location.href)
        return;

    WEBBrowser browser2;
    if (FAILED(browser2.CreateInstance()))
        return;

    int iWindowWidth = 500, iWindowHeight = 400;
    browser2.Left = (::GetSystemMetrics(SM_CXSCREEN)/2)-(iWindowWidth/2);
    browser2.Width = iWindowWidth;
    browser2.Top = (::GetSystemMetrics(SM_CYSCREEN)/2)-(iWindowHeight/2);
    browser2.Height = iWindowHeight;

    browser2.HideAllBars();

    CString strURL = ::GetString(IDS_GOOGLE_PLUS_SHARE_URL);
    strURL.Replace(_T("%url%"), EncodeURL((CString)m_browser.document.location.href));

    if (SUCCEEDED(browser2.Navigate(strURL, false)))
        browser2.Visible = true;
}

bool CGooglePlusOneButtonToolbar::IsApiScriptIncluded(const HTML::Document& doc)
{
    if (!doc)
        return false;

    for (HTML::Collection<HTML::ScriptElement>::Iterator it = doc.getElementsByTagName<HTML::ScriptElement>(L"script").GetIterator(); (bool)it; ++it)
    {
        if (!(*it).src)
            continue;

        if (m_google_plus_one_api_url == (*it).src)
            return true;
    }

    return false;
}

void CGooglePlusOneButtonToolbar::InjectButton(HTML::Document& doc)
{
    if (doc.getElementById(DIV_ID))
        return;

    bool bApiScriptIncluded = IsApiScriptIncluded(doc);
    if (bApiScriptIncluded && FindPlusOneButton())
        return;

    HTML::Element div = doc.createElement(L"div");
    div.id = DIV_ID;
    // hide button
    div.style.position = L"absolute";
    div.style.filter = L"alpha(opacity: 0)";
    div.style.zIndex = L"-1";
    div.style.width = L"1px";
    div.style.maxWidth = L"1px";
    div.style.height = L"1px";
    div.style.maxHeight = L"1px";
    div.style.top = L"0";
    div.style.left = L"0";

    HTML::Frame ifrm = doc.createElement(L"iframe");
    ifrm.style.width = L"1px";
    ifrm.style.maxWidth = L"1px";
    ifrm.style.height = L"1px";
    ifrm.style.maxHeight = L"1px";
    CString strURL = ::GetString(IDS_GOOGLE_PLUS_ONE_FRAME_URL);
    strURL.Replace(_T("%url%"), EncodeURL((CString)doc.location.href));
    ifrm.src = strURL; 

    div.appendChild(ifrm);
    doc.body.appendChild(div);
}

HTML::Element CGooglePlusOneButtonToolbar::FindChild(HTML::Element& elem, LPCWSTR pszTag)
{
    if (!elem)
        return HTML::Element();

    if (elem.tagName == pszTag)
        return elem;

    if (!elem.childNodes)
        return HTML::Element();

    for (HTML::Collection<>::Iterator it = HTML::Collection<>(elem.childNodes).GetIterator(); (bool)it; ++it)
    {
        HTML::Element child = FindChild(*it, pszTag);
        if (child)
            return child;
    }

    return HTML::Element();
}

HTML::Element CGooglePlusOneButtonToolbar::FindPlusOneButton()
{
    if (!m_browserLastGooglePlusOne || !m_browserLastGooglePlusOne.document)
        return HTML::Element();

    HTML::Element btn = m_browserLastGooglePlusOne.document.getElementById(L"button");
    if (!btn)
        btn = FindChild(m_browserLastGooglePlusOne.document.body, L"A");

    return btn;
}

void CGooglePlusOneButtonToolbar::DisableToolbarButton()
{
    if (m_nIconIndex == 0 && m_strLabel.IsEmpty() && m_strTooltip.IsEmpty())
        return;

    DeleteButton(0);
    m_strLabel = m_strTooltip = _T("");
    m_nIconIndex = 0;
    AddButtons(sizeof(m_buttons)/sizeof(TBBUTTON), m_buttons);
}

bool CGooglePlusOneButtonToolbar::IsPressed(HTML::Element& b)
{
    if (!b)
        return false;

    CComVariant vtAriaPressed = b.getAttribute(L"aria-pressed");
    if (vtAriaPressed.vt == VT_BSTR && vtAriaPressed.bstrVal && *vtAriaPressed.bstrVal)
    {
        if (M_TRUE == vtAriaPressed.bstrVal)
            return true;
    }
    else
    {
        CStringW strClassName = (CStringW)L" " + b.className + L" ";
        if (!strClassName.IsEmpty())
        {
            strClassName = (CStringW)L" " + strClassName + L" ";
            if (strClassName.Find(L" Cg ") != -1)
                return true;
        }
    }

    return false;
}

void CGooglePlusOneButtonToolbar::UpdateToolbarButton()
{
    HTML::Element b = FindPlusOneButton();
    if (!b)
        DisableToolbarButton();
    else
    {

        int nIconIndex = IsPressed(b) ? 1 : 0;
        CString strLabel, strTooltip;

        TBBUTTON tbButton = {0, IDM_GOOGLEPLUSONE, TBSTATE_ENABLED, BTNS_BUTTON | BTNS_AUTOSIZE};

        HTML::Element countDiv = m_browserLastGooglePlusOne.document.getElementById(L"aggregateCount");
        if (countDiv)
        {
            CComBSTR bsInnerText = countDiv.innerText;
            if (bsInnerText)
            {
                strLabel = bsInnerText;

                if (!strLabel.Trim().IsEmpty())
                    strLabel.Insert(0, _T(' '));
            }
        }

        if (nIconIndex != m_nIconIndex || m_strLabel != strLabel)
        {
            DeleteButton(0);

            tbButton.iBitmap = m_nIconIndex = nIconIndex;
            if ((m_strLabel = strLabel).IsEmpty())
                tbButton.iString = -1;
            else
            {
                tbButton.fsStyle |= BTNS_SHOWTEXT;
                tbButton.iString = (INT_PTR)(LPCTSTR)m_strLabel;
            }

            AddButtons(1, &tbButton);
        }

        CString strTitle;
        if (b.title)
            strTitle = b.title;

        HTML::Element bubbleDiv = m_browserLastGooglePlusOne.document.getElementById(L"aggregateBubble");
        if (!bubbleDiv)
            strTooltip = strTitle;
        else
        {
            CComVariant vtTitle = bubbleDiv.getAttribute(L"title");

            if (vtTitle.vt != VT_BSTR)
                strTooltip = b.title;
            else
                strTooltip = strTitle + L"\n" + vtTitle.bstrVal;
        }

        if (m_strTooltip != strTooltip)
            m_strTooltip = strTooltip;
    }
}

void CGooglePlusOneButtonToolbar::BeforeNavigate2(IDispatch* pDisp, VARIANT* URL)
{
    if (m_browser == pDisp)
    {
        StopTimer();

        DisableToolbarButton();

        m_browserLastGooglePlusOne.Release();
    }
}

void CGooglePlusOneButtonToolbar::DocumentComplete(IDispatch* pDisp, VARIANT* URL)
{
#ifdef _DEBUG
		if (URL && (URL->vt & VT_BSTR) && URL->bstrVal)
			ATLTRACE( _T("CGooglePlusOneButtonToolbar::DocumentComplete %s\n"), CString(URL->bstrVal) );
		else
			ATLTRACE( _T("CGooglePlusOneButtonToolbar::DocumentComplete - Cannot read location\n") );
#endif

    if (!pDisp)
		return;

    CString strURL;
    if (URL && (URL->vt & VT_BSTR) && URL->bstrVal)
        strURL = URL->bstrVal;

    if (!(0 == _tcsnicmp(strURL, _T("http://"), 7) ||  0 == _tcsnicmp(strURL, _T("https://"), 8)))
        return;

    if (m_browser.document.location.href == URL->bstrVal) // it's top level
    {
        WEBBrowser browser(pDisp);
        if (!browser || !browser.document || !browser.document.body)
            return;

        InjectButton(browser.document);
    }

    if (strURL.Find(m_google_plus_one_frame_domain) != -1)
    {
        if (strURL.Find(_T("/fastbutton")) != -1)
        {
            m_browserLastGooglePlusOne = pDisp;

            UpdateToolbarButton();
        }
        else
        {
            StartTimer();
        }
    }
}

void CGooglePlusOneButtonToolbar::StartTimer()
{
    if (!m_nTimerId)
        m_nTimerId = SetTimer(ID_UPDATE_BUTTON, 500);

    m_nTimerRepeatCount = 0;
}

void CGooglePlusOneButtonToolbar::StopTimer()
{
    if (m_nTimerId) KillTimer(m_nTimerId), m_nTimerId = 0;
    m_nTimerRepeatCount = 0;
}

void CGooglePlusOneButtonToolbar::OnTimer(UINT_PTR nTimerId)
{
    if (nTimerId != ID_UPDATE_BUTTON)
        return;

    if (++m_nTimerRepeatCount > 5)
        StopTimer();
    else
        UpdateToolbarButton();
}
